local require = require
local setmetatable = setmetatable
local error = error
local string = string
local table = table
local pairs = pairs
local type = type
local ngx = ngx
local tonumber = tonumber
local tostring = tostring
local io = io
local math = math
local bit = require "bit"
local os = os
local json = require 'cjson'
local unpack = unpack
local util = require 'util'
local ffi = require("ffi")
local pcall = pcall
package.cpath = "/opt/xyitech/jieyan-backend-production/xbit/?.so;;"
local xbit = require("xbit")
module(...)

_VERSION = '0.1'


 _int32_t = 0
 _int16_t = 1
 _float = 2
 _uint32_t = 4
 _uint16_t = 5
 _uint8_t = 6
 _int8_t = 7
 _dynamic = 99

 function multi(multilier)
    return function (n, condition)
            -- ngx.log(ngx.ERR, n)
            -- ngx.log(ngx.ERR, condition)
            if condition then
                return tonumber(n) / multilier
            else
                -- ngx.log(ngx.ERR, tonumber(n) * multilier)
                return tonumber(n) * multilier
            end
        end
 end

 function command101(index)
    return function (value, condition, commandId)
        -- ngx.log(ngx.ERR, value, ":", condition, ":", commandId)
        if commandId == 7 then
            if util.contains({1, 2}, index) then
                return multi(10000000)(value, condition)
            else
                return multi(1)(value, condition)
            end
        elseif commandId == 8 then
            if util.contains({1, 2, 5, 8}, index) then
                return multi(10000000)(value, condition)
            elseif util.contains({3, 4, 6, 7}, index) then
                return multi(1000)(value, condition)
            else
                return multi(1)(value, condition)
            end
        elseif commandId == 9 then
            if util.contains({1, 2}, index) then
                return multi(1000)(value, condition)
            else
                return multi(1)(value, condition)
            end
        elseif commandId == 18 then
            if util.contains({1, 2}, index) then
                return multi(100)(value, condition)
            else
                return multi(1)(value, condition)
            end
        else
            return multi(1)(value, condition)
        end
    end
 end


 function command9(index)
    return function (value, condition, commandId)
        -- ngx.log(ngx.ERR, value, ":", condition, ":", commandId)
        if commandId == 2 then
            if util.contains({1, 2}, index) then
                return multi(100)(value, condition)
            end
        elseif commandId == 7 then
            if util.contains({3}, index) then
                return multi(100)(value, condition)
            end
        elseif commandId == 8 then
            if util.contains({1, 2}, index) then
                return multi(100)(value, condition)
            end
        elseif commandId == 9 then
            if util.contains({2, 1}, index) then
                return multi(100)(value, condition)
            end
        elseif commandId == 11 then
            if util.contains({2, 1}, index) then
                return multi(100)(value, condition)
            end
        elseif commandId == 12 then
            if util.contains({2, 1}, index) then
                return multi(100)(value, condition)
            end
        elseif commandId == 13 then
            if util.contains({2, 1}, index) then
                return multi(100)(value, condition)
            end
        elseif commandId == 14 then
            if util.contains({2, 1}, index) then
                return multi(100)(value, condition)
            end
        elseif commandId == 15 then
            if util.contains({2, 1}, index) then
                return multi(100)(value, condition)
            end
        elseif commandId == 17 then
            if util.contains({2, 1}, index) then
                return multi(100)(value, condition)
            end
        elseif commandId == 18 then
            if util.contains({2, 1}, index) then
                return multi(100)(value, condition)
            end
        end
        return multi(1)(value, condition)
    end
 end

function transSid()
    return function (value, condition, commandId)
        -- ngx.log(ngx.ERR, value)
        -- ngx.log(ngx.ERR, condition)
        if condition then
            return bit.band(value, 0x7fffffff)
        else
            -- ngx.log(ngx.ERR, bit.arshift(bit.lshift(value, 1), 1))
            return bit.bor(value, 0x80000000)
        end
    end
end
function getString(size)
    return function (str)
        return string.sub(str, 1, xbit.readBuff(size, str, 0))
    end
end

function setUid()
    return function (v, condition, commandId)
            if condition then
                return os.time() .. math.random(0,9)
            else
                return os.time() .. math.random(0,9)
            end
        end
end
-- local _n = 'n'
_fields = {
    ["msgId"] = {["size"]= _uint8_t},
    ["fid"] = {["size"]= _uint8_t},
    ["sid"] = {["size"]= _uint8_t},
    ["fid_32"] = {["size"]= _uint32_t},
    ["sid_32"] = {["size"]= _uint32_t, ["function"] = transSid()},
    ["cid"] = {["size"]= _uint8_t},
    ["target_system"] = {["size"]= _uint8_t},
    ["command"] = {["size"]= _uint16_t},
    ["comfirmation"] = {["size"]= _uint8_t},
    ["command_param_1"] = {["size"]= _int32_t, ["function"] = command101(1)},
    ["command_param_2"] = {["size"]= _int32_t, ["function"] = command101(2)},
    ["command_param_3"] = {["size"]= _int32_t, ["function"] = command101(3)},
    ["command_param_4"] = {["size"]= _int32_t, ["function"] = command101(4)},
    ["command_param_5"] = {["size"]= _int32_t, ["function"] = command101(5)},
    ["command_param_6"] = {["size"]= _int32_t, ["function"] = command101(6)},
    ["command_param_7"] = {["size"]= _int32_t, ["function"] = command101(7)},
    ["command_param_8"] = {["size"]= _int32_t, ["function"] = command101(8)},
    ["command_param_201_1"] = {["size"]= _int32_t},
    ["command_param_201_2"] = {["size"]= _int32_t},
    ["command_param_201_3"] = {["size"]= _int32_t},
    ["command_param_201_4"] = {["size"]= _int32_t},
    ["command_param_201_5"] = {["size"]= _int32_t},
    ["command_param_201_6"] = {["size"]= _int32_t},
    ["command_param_201_7"] = {["size"]= _int32_t},
    ["command_param_201_8"] = {["size"]= _int32_t},
    ["param_f"] = {["size"]= _float},
    ["result"] = {["size"]= _uint8_t},
    ["count_8"] = {["size"]= _uint8_t},
    ["count_16"] = {["size"] = _uint16_t},
    ["frame"] = {["size"]= _uint8_t},
    ["mode"] = {["size"] = _uint8_t},
    ["status"] = {["size"] = _uint8_t},
    ["version"] = {["size"] = _uint8_t},
    ["timestamp"] = {["size"] = _uint32_t},
    ["fix_type"] = {["size"] = _uint8_t},
    ["lat_gps"] = {["size"] = _int32_t, ["function"] = multi(1e7)},
    ["lon_gps"] = {["size"] = _int32_t, ["function"] = multi(1e7)},
    ["alt_gps"] = {["size"] = _int32_t, ["function"] = multi(1e3)},
    ["alt_rel_pres"] = {["size"] = _int32_t, ["function"] = multi(1e3)},

    ["eph"] = {["size"] = _uint16_t},
    ["epv"] = {["size"] = _uint16_t},
    ["vel_gps"] = {["size"] = _uint16_t, ["function"] = multi(1e2)},
    ["cog"] = {["size"] = _uint16_t, ["function"] = multi(1e2)},
    ["sate_number"] = {["size"] = _uint8_t},
    ["x_lat"] = {["size"] = _int32_t, ["function"] = multi(10000000)},
    ["y_lon"] = {["size"] = _int32_t, ["function"] = multi(10000000)},
    ["z_alt"] = {["size"] = _int32_t, ["function"] = multi(1000)},
    ["v"] = {["size"] = _int32_t, ["function"] = multi(1000)},
    ["x"] = {["size"] = _float},
    ["y"] = {["size"] = _float},
    ["z"] = {["size"] = _float},
    ["vx"] = {["size"] = _float},
    ["vy"] = {["size"] = _float},
    ["vz"] = {["size"] = _float},
    ["height_diff"] = {["size"] = _int32_t},
    ["dis_m"] = {["size"] = _float},
    ["voltage"] = {["size"] = _uint16_t},
    ["voltages"] = {["subfields"] = {"voltage", "voltage", "voltage", "voltage", "voltage", "voltage","voltage", "voltage", "voltage", "voltage"}},
    ["current_battery"] = {["size"] = _int16_t, ["function"] = multi(10)},
    ["battery_remaining"] = {["size"] = _int8_t},
    ["fc_output_ave"] = {["size"] = _uint16_t},
    ["relative_alt"] = {["size"] = _int32_t, ["function"] = multi(1000)},
    ["pitch_deg"] = {["size"] = _int16_t, ["function"] = multi(10)},
    ["roll_deg"] = {["size"] = _int16_t, ["function"] = multi(10)},
    ["hdg"] = {["size"] = _uint16_t, ["function"] = multi(100)},
    ["image_id"] = {["size"] = _uint32_t},
    ["image_piece_num"] = {["size"] = _uint8_t},
    ["image_piece_id"] = {["size"] = _uint8_t},
    ["image_piece_data"] = {["size"] = _dynamic},
    ["offset_x"] = {["size"] = _float},
    ["offset_y"] = {["size"] = _float},
    ["type"] = {["size"] = _uint8_t},
    -- ["string_len"] = {["size"] = _uint16_t},
    ["string"] = {["size"] = _dynamic, ["function"] = getString(_uint16_t)},
    -- ["uid_str"] = {["subfields"] = {"string_len", "string"}}
    ["command_params101"] = {["subfields"] = {"command_param_1", "command_param_2", "command_param_3", "command_param_4", "command_param_5", "command_param_6", "command_param_7", "command_param_8"}},
    ["command_params201"] = {["subfields"] = {"command_param_201_1", "command_param_201_2", "command_param_201_3", "command_param_201_4", "command_param_201_5", "command_param_201_6", "command_param_201_7", "command_param_201_8"}},
    ["coordinates"] = {["subfields"]= {"x_lat", "y_lon", "z_alt", "v"}},
    ["msg_uid"] = {["size"] = _uint32_t, ["function"] = setUid()},
    ["msg_uid_recv"] = {["size"] = _uint32_t},
    ["abs_pre"] = {["size"] = _float},
    ["diff_pre"] = {["size"] = _float},
    ["pre_alt"] = {["size"] = _float},
    ["temp"] = {["size"] = _float},
    ["illumination"] = {["size"] = _float},
    ["angle_max"] = {["size"] = _int16_t},
    ["angle_min"] = {["size"] = _int16_t},
    ["angle_now"] = {["size"] = _int16_t},
    ["x_ctl"] = {["size"]= _int8_t},
    ["y_ctl"] = {["size"] = _int8_t},
    ["z_ctl"] = {["size"] = _int8_t},
    ["vision_reg_num"] = {["size"] = _uint8_t},
    ["vision_x_ned"] = {["size"] = _int16_t,  ["function"] = multi(100)},
    ["vision_y_ned"] = {["size"] = _int16_t,  ["function"] = multi(100)},
    ["vision_z_ned"] = {["size"] = _int16_t,  ["function"] = multi(5)},
    ["battery_life_factor"] = {["size"] = _uint8_t},
    ["uav_load_factor"] = {["size"] = _uint8_t},
    ["time_can_ﬂy"] = {["size"] = _uint16_t},
    ["time_needed"] = {["size"] = _uint16_t},
    ["ultra_alt"] = {["size"] = _uint16_t, ["function"] = multi(100)},
    ["actuator_throttle"] = {["size"] = _float},
    ["actuator_pitch"] = {["size"] = _float},
    ["actuator_roll"] = {["size"] = _float},
    ["actuator_yaw"] = {["size"] = _float},
    ["throttle_threshold_by_weight"] = {["size"] = _int32_t, ["function"] = multi(100)},

    ["alarm_id"] = {["size"] = _uint32_t},
    ["alarm_level"] = {["size"] = _uint8_t},

    ["alarm_action"] = {["size"] = _uint8_t},
    ["command_param_9_1"] = {["size"] = _int32_t, ["function"] = command9(1)},
    ["command_param_9_2"] = {["size"] = _int32_t, ["function"] = command9(2)},
    ["command_param_9_3"] = {["size"] = _int32_t, ["function"] = command9(3)},
    ["command_param_9_4"] = {["size"] = _int32_t, ["function"] = command9(4)},
    ["command_param_9_5"] = {["size"] = _int32_t, ["function"] = command9(5)},
    ["command_params9"] = {["subfields"] = {"command_param_9_1", "command_param_9_2", "command_param_9_3", "command_param_9_4", "command_param_9_5"}},
}
_protocal = {
    [1] = {"msgId", "fid_32", "timestamp","cid", "mode", "status", "version"},
    [2] = {"msgId", "fid_32", "timestamp", "voltages", "current_battery", "battery_remaining", "battery_life_factor", "uav_load_factor", "time_can_ﬂy", "time_needed","fc_output_ave"},
    [3] = {"msgId", "fid_32", "timestamp", "x", "y", "z", "vx", "vy", "vz", "ultra_alt", "dis_m"},
    [4] = {"msgId", "fid_32", "timestamp", "relative_alt", "pitch_deg", "roll_deg", "hdg"},
    [5] = {"msgId", "fid_32", "timestamp", "fix_type", "lat_gps", "lon_gps", "alt_rel_pres", "eph", "epv", "vel_gps", "cog", "sate_number"},
    [6] = {"msgId", "fid_32", "timestamp", "vision_reg_num", "vision_x_ned", "vision_y_ned", "vision_z_ned"},
    [7] = {"msgId", "fid_32"},
    [8] = {"msgId", "fid_32", "timestamp", "actuator_throttle", "actuator_pitch", "actuator_roll", "actuator_yaw", "throttle_threshold_by_weight"},
    [9] = {"msgId", "fid_32", "timestamp", "msg_uid_recv", "alarm_id", "alarm_level", {"command_params9", 5}},
    [10] = {"msgId", "fid_32", "timestamp", "msg_uid_recv", "alarm_id"},
    [81] = {"msgId", "sid", "cid", "mode", "status", "version"},
    [101] = {"msgId", "fid_32", "msg_uid", "target_system", "command", "comfirmation", {"command_params101", 5}},
    [102] = {"msgId", "fid_32", "command", "result"},
    [105] = {"msgId", "fid_32", "msg_uid", "target_system", "count_16", "frame", {"coordinates", 5}},
   
    [106] = {"msgId", "fid_32", "target_system", "type"},
    [107] = {"msgId", "fid_32", "msg_uid", "target_system", "mode"},
    [108] = {"msgId", "fid_32", "target_system", "type", "count_16", "frame", {"coordinates", 5}, "height_diff"},
    -- [109] = {"msgId", "fid_32", "timestamp"},
    [110] = {"msgId", "fid_32"},
    -- [111] = {"msgId", "fid_32", "len1", "version number", "len2", "hashcode"},
    -- [125] = {"msgId", "fid", "image_id", "image_piece_num", "image_piece_id", "image_piece_data"},
    [126] = {"msgId", "fid_32", "image_piece_data"},
    [127] = {"msgId", "fid_32", "offset_x", "offset_y"},
    -- [128] = {"msgId", "image_piece_data"},
    [151] = {"msgId", "sid_32", "cid", "mode", "status", "version"},
    [152] = {"msgId", "sid_32", "timestamp", "abs_pre", "diff_pre", "pre_alt", "temp", "illumination"},
    [153] = {"msgId", "sid_32", "timestamp", "angle_max", "angle_min", "angle_now"},
    [154] = {"msgId", "sid_32", "timestamp", "status", "x_ctl", "y_ctl", "z_ctl"},
    [155] = {"msgId", "sid_32"},
    [201] = {"msgId", "sid_32", "msg_uid", "target_system", "command", "comfirmation", {"command_params201", 5}},
    [202] = {"msgId", "sid_32", "command", "result"},
    [203] = {"msgId", "sid_32", "timestamp"},
    [245] = {"msgId", "fid_32", "type"},
    [246] = {"msgId", "fid_32", "timestamp"},
    [247] = {"msgId"},
    [248] = {"msgId", "fid_32", "status"},
    [249] = {"msgId", "fid_32", "string"},
    [250] = {"msgId", "fid_32", "string"},
    [251] = {"msgId", "fid_32", "string"},
    [252] = {"msgId", "fid_32"},
    [253] = {"msgId", "fid_32", "string"},
    [254] = {"msgId", "fid_32", "status"},
    [255] = {"msgId", "string"},
}

local _floats = {1,2,3,4}
-- return pcall(xbit.tobit2, args) end
function toBit(table)
    if not table or #table == 0 then
        return nil
    end
    local commandId = nil
    local msgId = table[1]
    local args = {}
    local indexAdjust = 0
    local thisProtocal = _protocal[table[1]]
    if not thisProtocal then
        return {}
    end
    local fieldDetail = {}
    --fill fieldDetail with size
    for i = 1, #thisProtocal do
        local field = thisProtocal[i]
        if type(field) == "string" then
            getFieldDetails(field, fieldDetail)
        elseif type(field) == "table" then
            local repeatOn = field[2]
            field = field[1]
            local count = table[repeatOn + indexAdjust]
            if msgId == 101 or msgId == 201 or msgId == 9 then
                commandId = count
                getFieldDetails(field, fieldDetail)
            else
                for k = 1, count do
                    getFieldDetails(field, fieldDetail)
                end
            end
            indexAdjust = indexAdjust + #fieldDetail
        else 
            ngx.log(ngx.ERR, "#############field missing############" .. field)
        end
    end
    -- make size,table[i] pairs to get bit value
    -- ngx.log(ngx.ERR, commandId)
    for j = 1, #fieldDetail do
        if j > #table then break end
        if msgId == 101 and util.contains(_floats, commandId) and j > #fieldDetail - 8 then
            args[#args + 1] = _float
        else
            args[#args + 1] = fieldDetail[j]["size"]
        end
        
        table[j] = tonumber(table[j]) or 0
        if fieldDetail[j]["function"] then
            if commandId then
                -- ngx.log(ngx.ERR, fieldDetail[j]["function"](table[j], false, commandId))
                args[#args + 1] = fieldDetail[j]["function"](table[j], false, commandId)
            else
                args[#args + 1] = fieldDetail[j]["function"](table[j], false)
            end
        else 
            args[#args + 1] = table[j]
        end
    end
    -- ngx.log(ngx.ERR, json.encode(args))
    local buff = xbit.tobit2(unpack(args))
    local prefix = xbit.tobit2(unpack({_uint32_t, #buff}))
    local final = prefix .. buff
    -- ngx.log(ngx.ERR, xbit.readBuff(_uint32_t, prefix, 0))
    -- ngx.log(ngx.ERR, prefix)
    -- ngx.log(ngx.ERR, final)
    -- ngx.log(ngx.ERR, xbit.tohex(final))
    return final
end

function toTable(bits)
    local args = {}
    local indexAdjust = 0
    local msgId = xbit.readBuff(_uint8_t, bits, 0)
    -- ngx.log(ngx.ERR, msgId)
    local thisProtocal = _protocal[msgId]
    local commandId = nil
    if not thisProtocal then return {} end
    -- ngx.log(ngx.ERR, "_protocal len is " .. #thisProtocal)
    for i = 1, #thisProtocal do
        local field = thisProtocal[i]
        if type(field) == "string" then
            getFieldDetails(field, args)
        elseif type(field) == "table" then
            local repeatOn = field[2]
            field = field[1]
            if not repeatOn or not args[repeatOn + indexAdjust] then
                ngx.log(ngx.ERR, "#############repeatOn error ############" .. json.encode(field))
            end

                local postion = _getRepeatOnPos(args, repeatOn + indexAdjust)

            -- ngx.log(ngx.ERR, "fieldDetail postion " .. postion)
            -- ngx.log(ngx.ERR, "args postion" .. args[repeatOn + indexAdjust]["size"])
                local count = xbit.readBuff(args[repeatOn + indexAdjust]["size"], bits, postion)
            -- ngx.log(ngx.ERR, "fieldDetail count " .. count)
                if msgId == 101 or msgId == 201 or msgId == 9 then
                    commandId = count
                    getFieldDetails(field,args)
                else 
                    for k = 1, count do
                        getFieldDetails(field, args)
                    end
                end
                indexAdjust = indexAdjust + count
        else 
            ngx.log(ngx.ERR, "#############field missing############" .. field)
        end
    end

    local table = {}
    local p = 0
    -- ngx.log(ngx.ERR, #args)
    for j = 1, #args do
        local size = args[j]["size"]
        if msgId == 101 and util.contains(_floats, commandId) and j > #args - 8 then
            args[#args + 1] = _float
        end
        local thisValue, len = xbit.readBuff(size, bits, p)
        
        thisValue = thisValue or 0
        if tostring(thisValue) == 'nan' then
            thisValue = 0
        end
        -- ngx.log(ngx.ERR, tostring(thisValue))
        -- ngx.log(ngx.ERR, thisValue * 10)
        if args[j]["function"] then
            if commandId then
                table[#table + 1] = args[j]["function"](thisValue, true, commandId) or 0
            else
                table[#table + 1] = args[j]["function"](thisValue, true) or 0
            end
        else
            table[#table + 1] = thisValue or 0
        end 
        local s = xbit.sizeofInt(args[j]["size"])
        if s then
            p = p + s
        else
            p = p + len
        end
        -- ngx.log(ngx.ERR, "p :" .. p)
    end
    -- ngx.log(ngx.ERR, json.encode(args))
    -- ngx.log(ngx.ERR, type(table))
    -- if type(table) == "number"  or type(table) == ngx.NULL then
        -- ngx.log(ngx.ERR, msgId)
        -- ngx.log(ngx.ERR, bits)
        -- ngx.log(ngx.ERR, table)
    -- else
        -- ngx.log(ngx.ERR, #table)
        -- for i=1, #table do
        --     table[i] = table[i] or 0
        -- end
        -- ngx.log(ngx.ERR, json.encode(table))
    -- end
    return table    
end

function _getRepeatOnPos(args, repeatOn)
    local res = 0
    for k,v in pairs(args) do
        if k < repeatOn then
            res = res +  xbit.sizeofInt(tonumber(v["size"]))
        end
    end
    return res
end

function getFieldDetails(key, res)
    local field = _fields[key]
    if not field then
        return res
    elseif field["subfields"] then
        for i = 1, #field["subfields"] do
            getFieldDetails(field["subfields"][i], res)
        end
    else
        res[#res + 1] = field
    end
    return res
end

local mt = {
    __newindex = function (table, key, val)
        error('attempt to write to undeclared variable "' .. key .. '"')
    end
}
setmetatable(_M, mt)

